package com.snail.service;

import com.snail.model.pojo.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.time.Duration;
import java.util.List;
import java.util.function.Function;

/**
 * @author cici
 * @description 热点数据的查询
 * queryHotData函数，String key缓存中的唯一标识。Class<T> clazz，表示返回数据的类型。Function<String, T> fallbackFunction，回退查询逻辑，当缓存没有命中时，执行这个函数从数据库或 ELK 等后端获取数据。
 * getFromCache函数，从缓存拿数据。String key，缓存中的唯一标识。Class<T> clazz，用于告诉方法缓存的数据应该转换成哪种类型
 * @date 2025/8/14 11:08
 */
@Service
public class HotdataService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    // 添加日志声明
    private static final Logger logger = LoggerFactory.getLogger(HotdataService.class);
    // 缓存空值过期时间(短于正常缓存，用于防止缓存穿透)
    private static final Duration CACHE_NULL_TTL = Duration.ofMinutes(5);


    public <T> T queryHotData(String key, Class<T> clazz, Function<String, T> fallbackFunction) {
        // 1. 尝试从缓存获取
        T value = getFromCache(key, clazz);
        if (value != null) {
            return value;
        }

        // 2. 使用分布式锁防止缓存击穿
        String lockKey = "lock:" + key;
        try {
            // 尝试获取锁
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", Duration.ofSeconds(10));
            if (Boolean.TRUE.equals(locked)) {
                try {
                    // 双重检查，防止并发请求在等待锁期间已有数据 redis内部有一个监听函数 根据某些ttl过期等去修改 xxl-job 定时同步数据
                    // zset存储的商品id score

                    value = getFromCache(key, clazz);
                    if (value != null) {
                        return value;
                    }

                    // 3. 查询ELK
                    value = fallbackFunction.apply(key);

                    // 4. 写入缓存
                    if (value != null) {
                        // 正常数据缓存，使用随机过期时间防止雪崩
                        Duration ttl = getRandomTtl(Duration.ofHours(1), Duration.ofHours(3));
                        redisTemplate.opsForValue().set(key, value, ttl);
                    } else {
                        // 缓存空值防止穿透
                        redisTemplate.opsForValue().set(key, new NullValue(), CACHE_NULL_TTL);
                    }

                    return value;
                } finally {
                    // 释放锁
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未获取到锁，短暂等待后重试
                Thread.sleep(100);
                return queryHotData(key, clazz, fallbackFunction);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            logger.warn("Query interrupted for key: {}", key, e);
            throw new RuntimeException("Query interrupted", e);
        } catch (Exception e) {
            logger.error("Failed to query data for key: {}", key, e);
            // 降级处理，直接查询ELK
            return fallbackFunction.apply(key);
        }
    }

    /**
     * 从缓存获取数据
     */
    @SuppressWarnings("unchecked")
    private <T> T getFromCache(String key, Class<T> clazz) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value instanceof NullValue) {
            return null; // 缓存空值
        }
        return (T) value;
    }

    /**
     * 生成随机过期时间，防止雪崩
     */
    private Duration getRandomTtl(Duration min, Duration max) {
        long minSeconds = min.getSeconds();
        long maxSeconds = max.getSeconds();
        long randomSeconds = minSeconds + (long) (Math.random() * (maxSeconds - minSeconds));
        return Duration.ofSeconds(randomSeconds);
    }

    // 空值标记类
    private static class NullValue implements Serializable {
        private static final long serialVersionUID = 1L;
    }




}
